// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace System.Security.Cryptography.X509Certificates
{
    internal sealed class OpenSslX509CertificateReader : ICertificatePal
    {
        private static DateTimeFormatInfo? s_validityDateTimeFormatInfo;

        private SafeX509Handle _cert;
        private SafeEvpPKeyHandle? _privateKey;
        private X500DistinguishedName? _subjectName;
        private X500DistinguishedName? _issuerName;
        private string? _subject;
        private string? _issuer;

        public static ICertificatePal FromHandle(IntPtr handle)
        {
            if (handle == IntPtr.Zero)
                throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle));

            return new OpenSslX509CertificateReader(Interop.Crypto.X509UpRef(handle));
        }

        public static ICertificatePal FromOtherCert(X509Certificate cert)
        {
            Debug.Assert(cert.Pal != null);

            // Ensure private key is copied
            OpenSslX509CertificateReader certPal = (OpenSslX509CertificateReader)cert.Pal;
            return certPal.DuplicateHandles();
        }

        public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
        {
            Debug.Assert(password != null);

            ICertificatePal? cert;

            if (TryReadX509Der(rawData, out cert) ||
                TryReadX509Pem(rawData, out cert) ||
                OpenSslPkcsFormatReader.TryReadPkcs7Der(rawData, out cert) ||
                OpenSslPkcsFormatReader.TryReadPkcs7Pem(rawData, out cert))
            {
                Debug.Assert(cert is not null);
                return cert;
            }

            try
            {
                return X509CertificateLoader.LoadPkcs12Pal(
                    rawData,
                    password.DangerousGetSpan(),
                    keyStorageFlags,
                    X509Certificate.GetPkcs12Limits(fromFile: false, password));
            }
            catch (Pkcs12LoadLimitExceededException e)
            {
                throw new CryptographicException(
                    SR.Cryptography_X509_PfxWithoutPassword_MaxAllowedIterationsExceeded,
                    e);
            }
        }

        public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
        {
            ICertificatePal? pal;

            // If we can't open the file, fail right away.
            using (SafeBioHandle fileBio = Interop.Crypto.BioNewFile(fileName, "rb"))
            {
                Interop.Crypto.CheckValidOpenSslHandle(fileBio);

                pal = FromBio(fileBio);
            }

            if (pal == null)
            {
                try
                {
                    pal = X509CertificateLoader.LoadPkcs12PalFromFile(
                        fileName,
                        password.DangerousGetSpan(),
                        keyStorageFlags,
                        X509Certificate.GetPkcs12Limits(fromFile: true, password));
                }
                catch (Pkcs12LoadLimitExceededException e)
                {
                    throw new CryptographicException(
                        SR.Cryptography_X509_PfxWithoutPassword_MaxAllowedIterationsExceeded,
                        e);
                }
            }

            return pal;
        }

        private static ICertificatePal? FromBio(SafeBioHandle bio)
        {
            int bioPosition = Interop.Crypto.BioTell(bio);

            Debug.Assert(bioPosition >= 0);

            ICertificatePal? certPal;
            if (TryReadX509Der(bio, out certPal))
            {
                return certPal;
            }

            // Rewind, try again.
            RewindBio(bio, bioPosition);

            if (TryReadX509Pem(bio, out certPal))
            {
                return certPal;
            }

            // Rewind, try again.
            RewindBio(bio, bioPosition);

            if (OpenSslPkcsFormatReader.TryReadPkcs7Der(bio, out certPal))
            {
                return certPal;
            }

            // Rewind, try again.
            RewindBio(bio, bioPosition);

            if (OpenSslPkcsFormatReader.TryReadPkcs7Pem(bio, out certPal))
            {
                return certPal;
            }

            return null;
        }

        internal static void RewindBio(SafeBioHandle bio, int bioPosition)
        {
            int ret = Interop.Crypto.BioSeek(bio, bioPosition);

            if (ret < 0)
            {
                throw Interop.Crypto.CreateOpenSslCryptographicException();
            }
        }

        internal static bool TryReadX509Der(ReadOnlySpan<byte> rawData, [NotNullWhen(true)] out ICertificatePal? certPal)
        {
            SafeX509Handle certHandle = Interop.Crypto.DecodeX509(
                ref MemoryMarshal.GetReference(rawData),
                rawData.Length);

            if (certHandle.IsInvalid)
            {
                certHandle.Dispose();
                certPal = null;
                Interop.Crypto.ErrClearError();
                return false;
            }

            certPal = new OpenSslX509CertificateReader(certHandle);
            return true;
        }

        internal static bool TryReadX509Pem(SafeBioHandle bio, [NotNullWhen(true)] out ICertificatePal? certPal)
        {
            SafeX509Handle cert = Interop.Crypto.PemReadX509FromBioAux(bio);

            if (cert.IsInvalid)
            {
                cert.Dispose();
                certPal = null;
                Interop.Crypto.ErrClearError();
                return false;
            }

            certPal = new OpenSslX509CertificateReader(cert);
            return true;
        }

        internal static bool TryReadX509PemNoAux(SafeBioHandle bio, [NotNullWhen(true)] out ICertificatePal? certPal)
        {
            SafeX509Handle cert = Interop.Crypto.PemReadX509FromBio(bio);

            if (cert.IsInvalid)
            {
                cert.Dispose();
                certPal = null;
                Interop.Crypto.ErrClearError();
                return false;
            }

            certPal = new OpenSslX509CertificateReader(cert);
            return true;
        }

        internal static bool TryReadX509Pem(ReadOnlySpan<byte> rawData, [NotNullWhen(true)] out ICertificatePal? certPal)
        {
            using (SafeBioHandle bio = Interop.Crypto.CreateMemoryBio())
            {
                Interop.Crypto.CheckValidOpenSslHandle(bio);

                if (Interop.Crypto.BioWrite(bio, rawData) != rawData.Length)
                {
                    Interop.Crypto.ErrClearError();
                }

                return TryReadX509Pem(bio, out certPal);
            }
        }

        internal static bool TryReadX509Der(SafeBioHandle bio, [NotNullWhen(true)] out ICertificatePal? fromBio)
        {
            SafeX509Handle cert = Interop.Crypto.ReadX509AsDerFromBio(bio);

            if (cert.IsInvalid)
            {
                cert.Dispose();
                fromBio = null;
                Interop.Crypto.ErrClearError();
                return false;
            }

            fromBio = new OpenSslX509CertificateReader(cert);
            return true;
        }

        internal OpenSslX509CertificateReader(SafeX509Handle handle)
        {
            // X509_check_purpose has the effect of populating the sha1_hash value,
            // and other "initialize" type things.
            bool init = Interop.Crypto.X509CheckPurpose(handle, -1, 0);

            if (!init)
            {
                throw Interop.Crypto.CreateOpenSslCryptographicException();
            }

            _cert = handle;
        }

        public bool HasPrivateKey
        {
            get { return _privateKey != null; }
        }

        public IntPtr Handle
        {
            get { return _cert == null ? IntPtr.Zero : _cert.DangerousGetHandle(); }
        }

        internal SafeX509Handle SafeHandle
        {
            get { return _cert; }
        }

        public string Issuer
        {
            get
            {
                // IssuerName is mutable to callers in X509Certificate. We want to be
                // able to get the issuer even if IssuerName has been mutated, so we
                // don't use it here.
                return _issuer ??= UseCertInteriorData(static cert => {
                    return Interop.Crypto.LoadX500Name(Interop.Crypto.X509GetIssuerName(cert)).Name;
                });
            }
        }

        public string Subject
        {
            get
            {
                // SubjectName is mutable to callers in X509Certificate. We want to be
                // able to get the subject even if SubjectName has been mutated, so we
                // don't use it here.
                return _subject ??= UseCertInteriorData(static cert => {
                    return Interop.Crypto.LoadX500Name(Interop.Crypto.X509GetSubjectName(cert)).Name;
                });
            }
        }

        public string LegacyIssuer => IssuerName.Decode(X500DistinguishedNameFlags.None);

        public string LegacySubject => SubjectName.Decode(X500DistinguishedNameFlags.None);

        public byte[] Thumbprint
        {
            get
            {
                return Interop.Crypto.GetX509Thumbprint(_cert);
            }
        }

        public string KeyAlgorithm
        {
            get
            {
                return UseCertInteriorData(static cert => {
                    IntPtr oidPtr = Interop.Crypto.GetX509PublicKeyAlgorithm(cert);
                    return Interop.Crypto.GetOidValue(oidPtr);
                });
            }
        }

        public byte[]? KeyAlgorithmParameters
        {
            get
            {
                return Interop.Crypto.GetX509PublicKeyParameterBytes(_cert);
            }
        }

        public byte[] PublicKeyValue
        {
            get
            {
                return UseCertInteriorData(static cert => {
                    IntPtr keyBytesPtr = Interop.Crypto.GetX509PublicKeyBytes(cert);
                    return Interop.Crypto.GetAsn1StringBytes(keyBytesPtr);
                });
            }
        }

        public byte[] SerialNumber
        {
            get
            {
                using (SafeSharedAsn1IntegerHandle serialNumber = Interop.Crypto.X509GetSerialNumber(_cert))
                {
                    return Interop.Crypto.GetAsn1IntegerBytes(serialNumber);
                }
            }
        }

        public string SignatureAlgorithm
        {
            get
            {
                return UseCertInteriorData(static cert => {
                    IntPtr oidPtr = Interop.Crypto.GetX509SignatureAlgorithm(cert);
                    return Interop.Crypto.GetOidValue(oidPtr);
                });
            }
        }

        public DateTime NotAfter
        {
            get
            {

                return UseCertInteriorData(static cert => {
                    return ExtractValidityDateTime(Interop.Crypto.GetX509NotAfter(cert));
                });
            }
        }

        public DateTime NotBefore
        {
            get
            {
                return UseCertInteriorData(static cert => {
                    return ExtractValidityDateTime(Interop.Crypto.GetX509NotBefore(cert));
                });
            }
        }

        public byte[] RawData
        {
            get
            {
                return Interop.Crypto.OpenSslEncode(
                    Interop.Crypto.GetX509DerSize,
                    Interop.Crypto.EncodeX509,
                    _cert);
            }
        }

        public int Version
        {
            get
            {
                int version = Interop.Crypto.GetX509Version(_cert);

                if (version < 0)
                {
                    throw new CryptographicException();
                }

                // The file encoding is v1(0), v2(1), v3(2).
                // The .NET answers are 1, 2, 3.
                return version + 1;
            }
        }

        public bool Archived
        {
            get { return false; }
            set
            {
                throw new PlatformNotSupportedException(
                    SR.Format(SR.Cryptography_Unix_X509_PropertyNotSettable, "Archived"));
            }
        }

        public string FriendlyName
        {
            get { return ""; }
            set
            {
                throw new PlatformNotSupportedException(
                  SR.Format(SR.Cryptography_Unix_X509_PropertyNotSettable, "FriendlyName"));
            }
        }

        public X500DistinguishedName SubjectName
        {
            get
            {
                return _subjectName ??= UseCertInteriorData(static cert => {
                    return Interop.Crypto.LoadX500Name(Interop.Crypto.X509GetSubjectName(cert));
                });
            }
        }

        public X500DistinguishedName IssuerName
        {
            get
            {
                return _issuerName ??= UseCertInteriorData(static cert => {
                    return Interop.Crypto.LoadX500Name(Interop.Crypto.X509GetIssuerName(cert));
                });
            }
        }

        public PolicyData GetPolicyData()
        {
            return UseCertInteriorData(static cert => {
                PolicyData policyData = default;

                int extensionCount = Interop.Crypto.X509GetExtCount(cert);

                for (int i = 0; i < extensionCount; i++)
                {
                    IntPtr ext = Interop.Crypto.X509GetExt(cert, i);
                    Interop.Crypto.CheckValidOpenSslHandle(ext);

                    IntPtr oidPtr = Interop.Crypto.X509ExtensionGetOid(ext);
                    Interop.Crypto.CheckValidOpenSslHandle(oidPtr);
                    string oidValue = Interop.Crypto.GetOidValue(oidPtr);

                    IntPtr dataPtr = Interop.Crypto.X509ExtensionGetData(ext);
                    Interop.Crypto.CheckValidOpenSslHandle(dataPtr);

                    switch (oidValue)
                    {
                        case Oids.ApplicationCertPolicies:
                            policyData.ApplicationCertPolicies = Interop.Crypto.GetAsn1StringBytes(dataPtr);
                            break;
                        case Oids.CertPolicies:
                            policyData.CertPolicies = Interop.Crypto.GetAsn1StringBytes(dataPtr);
                            break;
                        case Oids.CertPolicyMappings:
                            policyData.CertPolicyMappings = Interop.Crypto.GetAsn1StringBytes(dataPtr);
                            break;
                        case Oids.CertPolicyConstraints:
                        policyData.CertPolicyConstraints = Interop.Crypto.GetAsn1StringBytes(dataPtr);
                        break;
                        case Oids.EnhancedKeyUsage:
                            policyData.EnhancedKeyUsage = Interop.Crypto.GetAsn1StringBytes(dataPtr);
                            break;
                        case Oids.InhibitAnyPolicyExtension:
                            policyData.InhibitAnyPolicyExtension = Interop.Crypto.GetAsn1StringBytes(dataPtr);
                            break;
                    }
                }

                return policyData;
            });
        }

        public IEnumerable<X509Extension> Extensions
        {
            get
            {
                return UseCertInteriorData(static cert => {
                    int extensionCount = Interop.Crypto.X509GetExtCount(cert);
                    X509Extension[] extensions = new X509Extension[extensionCount];

                    for (int i = 0; i < extensionCount; i++)
                    {
                        IntPtr ext = Interop.Crypto.X509GetExt(cert, i);

                        Interop.Crypto.CheckValidOpenSslHandle(ext);

                        IntPtr oidPtr = Interop.Crypto.X509ExtensionGetOid(ext);

                        Interop.Crypto.CheckValidOpenSslHandle(oidPtr);

                        string oidValue = Interop.Crypto.GetOidValue(oidPtr);
                        Oid oid = new Oid(oidValue);

                        IntPtr dataPtr = Interop.Crypto.X509ExtensionGetData(ext);

                        Interop.Crypto.CheckValidOpenSslHandle(dataPtr);

                        byte[] extData = Interop.Crypto.GetAsn1StringBytes(dataPtr);
                        bool critical = Interop.Crypto.X509ExtensionGetCritical(ext);

                        extensions[i] = new X509Extension(oid, extData, critical);
                    }

                    return extensions;
                });
            }
        }

        internal static ArraySegment<byte> FindFirstExtension(SafeX509Handle cert, string oidValue)
        {
            int nid = Interop.Crypto.ResolveRequiredNid(oidValue);

            using (SafeSharedAsn1OctetStringHandle data = Interop.Crypto.X509FindExtensionData(cert, nid))
            {
                if (data.IsInvalid)
                {
                    return default;
                }

                return Interop.Crypto.RentAsn1StringBytes(data.DangerousGetHandle());
            }
        }

        internal void SetPrivateKey(SafeEvpPKeyHandle privateKey)
        {
            _privateKey = privateKey;
        }

        internal SafeEvpPKeyHandle? PrivateKeyHandle
        {
            get { return _privateKey; }
        }

        public RSA? GetRSAPrivateKey()
        {
            if (_privateKey == null || _privateKey.IsInvalid)
            {
                return null;
            }

            return new RSAOpenSsl(_privateKey);
        }

        public DSA? GetDSAPrivateKey()
        {
            if (_privateKey == null || _privateKey.IsInvalid)
            {
                return null;
            }

            return new DSAOpenSsl(_privateKey);
        }

        public ECDsa GetECDsaPublicKey()
        {
            using (SafeEvpPKeyHandle publicKeyHandle = Interop.Crypto.GetX509EvpPublicKey(_cert))
            {
                Interop.Crypto.CheckValidOpenSslHandle(publicKeyHandle);

                return new ECDsaOpenSsl(publicKeyHandle);
            }
        }

        public ECDiffieHellman GetECDiffieHellmanPublicKey()
        {
            using (SafeEvpPKeyHandle publicKeyHandle = Interop.Crypto.GetX509EvpPublicKey(_cert))
            {
                Interop.Crypto.CheckValidOpenSslHandle(publicKeyHandle);

                return new ECDiffieHellmanOpenSsl(publicKeyHandle);
            }
        }

        public ECDsa? GetECDsaPrivateKey()
        {
            if (_privateKey == null || _privateKey.IsInvalid)
            {
                return null;
            }

            return new ECDsaOpenSsl(_privateKey);
        }

        public ECDiffieHellman? GetECDiffieHellmanPrivateKey()
        {
            if (_privateKey == null || _privateKey.IsInvalid)
            {
                return null;
            }

            return new ECDiffieHellmanOpenSsl(_privateKey);
        }

        public MLDsa? GetMLDsaPrivateKey()
        {
            if (_privateKey == null || _privateKey.IsInvalid)
            {
                return null;
            }

            return new MLDsaOpenSsl(_privateKey);
        }

        public MLKem? GetMLKemPrivateKey()
        {
            if (_privateKey is null || _privateKey.IsInvalid)
            {
                return null;
            }

            return new MLKemOpenSsl(_privateKey);
        }

        public SlhDsa? GetSlhDsaPrivateKey()
        {
            if (_privateKey == null || _privateKey.IsInvalid)
            {
                return null;
            }

            return new SlhDsaOpenSsl(_privateKey);
        }

        private OpenSslX509CertificateReader CopyWithPrivateKey(SafeEvpPKeyHandle privateKey)
        {
            // This could be X509Duplicate for a full clone, but since OpenSSL certificates
            // are functionally immutable (unlike Windows ones) an UpRef is sufficient.
            SafeX509Handle certHandle = Interop.Crypto.X509UpRef(_cert);
            OpenSslX509CertificateReader duplicate = new OpenSslX509CertificateReader(certHandle);

            duplicate.SetPrivateKey(privateKey);
            return duplicate;
        }

        public ICertificatePal CopyWithPrivateKey(DSA privateKey)
        {
            DSAOpenSsl? typedKey = privateKey as DSAOpenSsl;

            if (typedKey != null)
            {
                return CopyWithPrivateKey(typedKey.DuplicateKeyHandle());
            }

            DSAParameters dsaParameters = privateKey.ExportParameters(true);

            using (PinAndClear.Track(dsaParameters.X!))
            using (typedKey = new DSAOpenSsl(dsaParameters))
            {
                return CopyWithPrivateKey(typedKey.DuplicateKeyHandle());
            }
        }

        public ICertificatePal CopyWithPrivateKey(ECDsa privateKey)
        {
            ECDsaOpenSsl? typedKey = privateKey as ECDsaOpenSsl;

            if (typedKey != null)
            {
                return CopyWithPrivateKey(typedKey.DuplicateKeyHandle());
            }

            ECParameters ecParameters = privateKey.ExportParameters(true);

            using (PinAndClear.Track(ecParameters.D!))
            using (typedKey = new ECDsaOpenSsl())
            {
                typedKey.ImportParameters(ecParameters);

                return CopyWithPrivateKey(typedKey.DuplicateKeyHandle());
            }
        }

        public ICertificatePal CopyWithPrivateKey(ECDiffieHellman privateKey)
        {
            ECDiffieHellmanOpenSsl? typedKey = privateKey as ECDiffieHellmanOpenSsl;

            if (typedKey != null)
            {
                return CopyWithPrivateKey(typedKey.DuplicateKeyHandle());
            }

            ECParameters ecParameters = privateKey.ExportParameters(true);

            using (PinAndClear.Track(ecParameters.D!))
            using (typedKey = new ECDiffieHellmanOpenSsl())
            {
                typedKey.ImportParameters(ecParameters);

                return CopyWithPrivateKey(typedKey.DuplicateKeyHandle());
            }
        }

        public ICertificatePal CopyWithPrivateKey(MLDsa privateKey)
        {
            if (privateKey is MLDsaImplementation impl)
            {
                return CopyWithPrivateKey(impl.DuplicateHandle());
            }

            if (privateKey is MLDsaOpenSsl implOpenSsl)
            {
                return CopyWithPrivateKey(implOpenSsl.DuplicateKeyHandle());
            }

            using (MLDsaImplementation clone = MLDsaImplementation.DuplicatePrivateKey(privateKey))
            {
                return CopyWithPrivateKey(clone.DuplicateHandle());
            }
        }

        public ICertificatePal CopyWithPrivateKey(MLKem privateKey)
        {
            switch (privateKey)
            {
                case MLKemOpenSsl implOpenSsl:
                    return CopyWithPrivateKey(implOpenSsl.DuplicateKeyHandle());
                case MLKemImplementation impl:
                    return CopyWithPrivateKey(impl.DuplicateHandle());
                default:
                    using (MLKemImplementation clone = MLKemImplementation.DuplicatePrivateKey(privateKey))
                    {
                        return CopyWithPrivateKey(clone.DuplicateHandle());
                    }
            }
        }

        public ICertificatePal CopyWithPrivateKey(SlhDsa privateKey)
        {
            if (privateKey is SlhDsaImplementation impl)
            {
                return CopyWithPrivateKey(impl.DuplicateHandle());
            }

            if (privateKey is SlhDsaOpenSsl implOpenSsl)
            {
                return CopyWithPrivateKey(implOpenSsl.DuplicateKeyHandle());
            }

            using (SlhDsaImplementation clone = SlhDsaImplementation.DuplicatePrivateKey(privateKey))
            {
                return CopyWithPrivateKey(clone.DuplicateHandle());
            }
        }

        public ICertificatePal CopyWithPrivateKey(RSA privateKey)
        {
            RSAOpenSsl? typedKey = privateKey as RSAOpenSsl;

            if (typedKey != null)
            {
                return CopyWithPrivateKey(typedKey.DuplicateKeyHandle());
            }

            RSAParameters rsaParameters = privateKey.ExportParameters(true);

            using (PinAndClear.Track(rsaParameters.D!))
            using (PinAndClear.Track(rsaParameters.P!))
            using (PinAndClear.Track(rsaParameters.Q!))
            using (PinAndClear.Track(rsaParameters.DP!))
            using (PinAndClear.Track(rsaParameters.DQ!))
            using (PinAndClear.Track(rsaParameters.InverseQ!))
            using (typedKey = new RSAOpenSsl(rsaParameters))
            {
                return CopyWithPrivateKey(typedKey.DuplicateKeyHandle());
            }
        }

        public string GetNameInfo(X509NameType nameType, bool forIssuer)
        {
            using (SafeBioHandle bioHandle = Interop.Crypto.GetX509NameInfo(_cert, (int)nameType, forIssuer))
            {
                if (bioHandle.IsInvalid)
                {
                    return "";
                }

                int bioSize = Interop.Crypto.GetMemoryBioSize(bioHandle);
                // Ensure space for the trailing \0
                Span<byte> buffer = new byte[bioSize + 1];
                Span<byte> current = buffer;
                int total = 0;
                int read;

                do
                {
                    read = Interop.Crypto.BioGets(bioHandle, current);

                    if (read < 0)
                    {
                        throw Interop.Crypto.CreateOpenSslCryptographicException();
                    }

                    current = current.Slice(read);
                    total += read;
                }
                while (read > 0);

                return Encoding.UTF8.GetString(buffer.Slice(0, total));
            }
        }

        public void AppendPrivateKeyInfo(StringBuilder sb)
        {
            if (!HasPrivateKey)
            {
                return;
            }

            // There's nothing really to say about the key, just acknowledge there is one.
            sb.AppendLine();
            sb.AppendLine();
            sb.AppendLine("[Private Key]");
        }

        public void Dispose()
        {
            if (_privateKey != null)
            {
                _privateKey.Dispose();
                _privateKey = null;
            }

            if (_cert != null)
            {
                _cert.Dispose();
                _cert = null!;
            }
        }

        internal OpenSslX509CertificateReader DuplicateHandles()
        {
            SafeX509Handle certHandle = Interop.Crypto.X509UpRef(_cert);
            OpenSslX509CertificateReader duplicate = new OpenSslX509CertificateReader(certHandle);

            if (_privateKey != null)
            {
                SafeEvpPKeyHandle keyHandle = _privateKey.DuplicateHandle();
                duplicate.SetPrivateKey(keyHandle);
            }

            return duplicate;
        }

        internal static DateTime ExtractValidityDateTime(IntPtr validityDatePtr)
        {
            byte[] bytes = Interop.Crypto.GetAsn1StringBytes(validityDatePtr);

            // RFC 5280 (X509v3 - https://tools.ietf.org/html/rfc5280)
            // states that the validity times are either UTCTime:YYMMDDHHMMSSZ (13 bytes)
            // or GeneralizedTime:YYYYMMDDHHMMSSZ (15 bytes).
            // Technically, both UTCTime and GeneralizedTime can have more complicated
            // representations, but X509 restricts them to only the one form each.
            //
            // Furthermore, the UTCTime year values are to be interpreted as 1950-2049.
            //
            // No mention is made in RFC 5280 of different rules for v1 or v2 certificates.

            Debug.Assert(bytes != null);
            Debug.Assert(
                bytes.Length == 13 || bytes.Length == 15,
                "DateTime value should be UTCTime (13 bytes) or GeneralizedTime (15 bytes)");
            Debug.Assert(
                bytes[bytes.Length - 1] == 'Z',
                "DateTime value should end with Z marker");

            if (bytes != null && bytes.Length is 13 or 15 && bytes[^1] == 'Z')
            {
                Span<char> dateString = stackalloc char[Encoding.ASCII.GetCharCount(bytes)];
                Encoding.ASCII.GetChars(bytes, dateString);

                if (s_validityDateTimeFormatInfo == null)
                {
                    DateTimeFormatInfo validityFormatInfo =
                        (DateTimeFormatInfo)CultureInfo.InvariantCulture.DateTimeFormat.Clone();

                    // Two-digit years are 1950-2049
                    validityFormatInfo.Calendar.TwoDigitYearMax = 2049;

                    s_validityDateTimeFormatInfo = validityFormatInfo;
                }

                if (DateTime.TryParseExact(
                    dateString,
                    bytes.Length == 13 ? "yyMMddHHmmss'Z'" : "yyyyMMddHHmmss'Z'",
                    s_validityDateTimeFormatInfo,
                    DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
                    out DateTime time))
                {
                    return time.ToLocalTime();
                }
            }

            throw new CryptographicException();
        }

        public byte[] Export(X509ContentType contentType, SafePasswordHandle password)
        {
            using (IExportPal storePal = StorePal.FromCertificate(this))
            {
                byte[]? exported = storePal.Export(contentType, password);
                Debug.Assert(exported != null);
                return exported;
            }
        }

        public byte[] ExportPkcs12(Pkcs12ExportPbeParameters exportParameters, SafePasswordHandle password)
        {
            using (IExportPal storePal = StorePal.FromCertificate(this))
            {
                return storePal.ExportPkcs12(exportParameters, password);
            }
        }

        public byte[] ExportPkcs12(PbeParameters exportParameters, SafePasswordHandle password)
        {
            using (IExportPal storePal = StorePal.FromCertificate(this))
            {
                return storePal.ExportPkcs12(exportParameters, password);
            }
        }

        private T UseCertInteriorData<T>(Func<SafeX509Handle, T> callback)
        {
            // Many of the reader's APIs perform two steps of getting an IntPtr to
            // interior data of the X509* object, then passing that IntPtr to some
            // other API that interprets the data in the pointer. If the SafeX509Handle
            // is disposed in between the two calls, then the data in the IntPtr no longer
            // points to valid data.
            // To keep the X509 object alive, manually increment the reference to it.
            bool addedRef = false;

            try
            {
                _cert.DangerousAddRef(ref addedRef);
                return callback(_cert);
            }
            finally
            {
                if (addedRef)
                {
                    _cert.DangerousRelease();
                }
            }
        }
    }
}
